ci: add caching to speed up tutor-based CI workflow#802
ci: add caching to speed up tutor-based CI workflow#802asadali145 with Copilot wants to merge 6 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR speeds up the Tutor-based integration test workflow by adding multiple caches (Python build artifacts, pip cache, edx-platform checkout, Tutor config, and a Docker image cache in GHCR) so repeated CI runs don’t rebuild everything from scratch.
Changes:
- Resolve the edx-platform branch tip SHA up front and use it to key subsequent caches.
- Cache
dist/artifacts, pip downloads, and the edx-platform checkout to reduce repeated network/build work. - Add a GHCR-backed Docker image cache and cache Tutor config dirs to potentially skip image builds and
tutor dev launch.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@copilot fix the failing integration tests in https://github.com/mitodl/open-edx-plugins/actions/runs/26108947537/job/76780542597, look for the reason and fix it. |
Fixed in 2895b45. The failure was in the "Push Docker image to registry cache" step: The workflow was constructing the dev image name by appending |
|
@copilot CI checks are still taking about 25 minutes on master branch. This includes about 15m for the tutor launch step. Looks like there is still some issue? Look at the latest CI run and see what can be fixed/improved. |
Investigated and fixed in 396604e. Two root causes were found: Root cause 1 — Tutor config cache never saved due to permission errors: Root cause 2 — Cache key included edx-platform SHA: Fixes:
On the next run after a cache miss, "Generate Tutor config" will take ~5 seconds and save cleanly. Subsequent runs will skip it entirely via cache hit. |
|
@copilot fix the pre-commit checks |
Fixed in b2f5f29. Two pre-commit hooks were failing:
|
|
@copilot now tests are failing with missing apps, I think tutor launch cannot be replaced with tutor config save. R&D on how tutor works with open edx and then make required changes |
Fixed in 276723d. Root cause: Without this egg-info, Fix: Added a step after |
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| packages: write |
There was a problem hiding this comment.
Fixed in 3509aa6.
GitHub Actions does not support conditional job-level permissions (permissions must be static YAML), so a full job split would require saving and uploading the Docker image as a tarball artifact (several GB per matrix combination) then downloading and loading it in a separate job — adding significant overhead.
Instead, the feedback is addressed by ensuring packages: write is never exercised during PR runs:
- The GHCR login step condition changed from
github.event_name != 'pull_request' || same-repo-pr→github.event_name == 'push': the GITHUB_TOKEN with write scope is never used during PRs (PRs pull from GHCR anonymously, which works for public packages) - The Docker push step condition simplified to
github.event_name == 'push': push never runs during PRs - A comment on
packages: writedocuments this design and the GitHub Actions limitation
Also fixed the Tutor config cache key that was split across lines (reviewer comment at line 166-167).
|
@copilot rebase this branch with main. |
0cd737e to
5491e79
Compare
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
4a73bd4 to
88503b5
Compare
|
@copilot revisit all the changes in this PR and tell me why it took 9-15m initially after optimizations but as we did more iterations the time is not again over 20m. |
…-environment-steps
Done — merged
The root cause is that the Docker image cache never works for PR runs. Looking at the most recent successful CI run, the two dominant steps are:
Total: ~26 min. Why the Docker cache misses every time: The Additionally, the GHCR cache tag encodes the edx-platform SHA: Why "9-15m initially" → "20+ min now": The "9-15m" was the time saved when To actually get Docker cache hits on PR runs, one of the following is needed:
Would you like me to implement one of these approaches? |
What are the relevant tickets?
N/A
Description (What does it do?)
Adds several caching layers to
.github/workflows/ci.ymlto avoid rebuilding the Tutor environment from scratch on every push. The biggest time sinks were the Docker image build (~15–20 min), the edx-platform clone (~5–10 min), and Tutor config generation (~9–15 min).Changes:
Resolve edx-platform tip SHA early — a cheap
git ls-remotecall (no download) gives the current HEAD SHA for the branch; used as cache key across all subsequent caches.Cache built
dist/packages — keyed by a hash of all files undersrc/**,pyproject.toml, anduv.lock(including static assets and templates); skipsuv build --all-packageson hits.Cache pip packages — caches
~/.cache/pipkeyed by branch + OS; speeds up repeatedpip install tutor>=…calls.Shallow-clone edx-platform + cache the clone dir — replaces the full two-step clone+checkout with a single
git clone --depth=1 --branch=…and caches the directory keyed by branch+SHA; skips the clone entirely on hits.Docker image cache via ghcr.io — before building, tries to pull
ghcr.io/mitodl/openedx-dev-cache:<branch-sha>. On hit, retags the pulled image and skipstutor images build openedx-dev. On miss, builds normally then pushes to ghcr.io for future runs usingtutor config printvalue DOCKER_IMAGE_OPENEDX_DEVto resolve the correct image name. Push is conditioned onpushevents only (never onpull_requestruns). The GHCR namespace is hardcoded viaenv.GHCR_CACHE_OWNER: mitodlso fork PRs always pull from the upstream org's cache.Cache Tutor config directory — caches
~/.local/share/tutorand~/.local/share/tutor-mainkeyed by Tutor version + branch; skipstutor config saveentirely on hits (the docker-compose env files are already present from the cache).Generate edx-platform egg-info on the host runner — runs
pip install --no-deps -e /path/to/edx-platformafter the edx-platform is cloned/restored. When edx-platform is bind-mounted into the Tutor container, the image'sOpen_edX.egg-info/directory is overwritten by the host checkout (which has no egg-info). Without the egg-info,pkg_resourcescannot read the edx-platform'slms.djangoappentry points, soget_plugin_apps(ProjectType.LMS)fails to discover apps likecontent_libraries, causing aRuntimeErrorat Django startup. Previously,tutor dev init(viamounted-directories.sh) regenerated this egg-info inside a running container; this step reproduces that regeneration in ~5 seconds on the host without starting any containers.Estimated savings per run on cache hit:
How can this be tested?
tutor config save.ghcr.io/mitodl/openedx-dev-cachepackage is created in the org's packages after the first run on the main branch.Additional Context
packages: writepermission is declared at the job level (GitHub Actions does not support conditional job-level permissions). However, it is never exercised duringpull_requestruns: both the GHCR login step and the Docker push step are conditioned ongithub.event_name == 'push'. PRs pull from GHCR using anonymous access (the cache package is public-readable).tutor dev launch -I --skip-buildwas replaced bytutor config savebecause the former starts Docker containers (MongoDB, MySQL, etc.) that write data files owned by root. Those root-owned files causedtar: Permission deniederrors in the post-cache step, preventing the Tutor config cache from ever being saved.tutor config savegenerates all needed docker-compose files in ~5 seconds without starting any containers.Open_edX.egg-info/is generated on the host runner rather than inside a container. The egg-info directory is a portable text-format metadata directory;pkg_resourcesinside the container reads it correctly via the bind mount regardless of which Python generated it. The egg-info is also captured in the edx-platform directory cache, so on cache hits it is already present.actions/cache@v4(SHA-pinned) anddocker/login-action@v3(SHA-pinned) are the only new actions added.